package com.guosen.zebra.framework.starter.s3.service.impl;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.guosen.zebra.framework.starter.s3.exception.S3Exception;
import com.guosen.zebra.framework.starter.s3.properties.S3Properties;
import com.guosen.zebra.framework.starter.s3.service.ZebraS3Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.stream.Collectors;

/**
 * S3 实现类
 */
@Slf4j
public class ZebraS3ServiceImpl implements ZebraS3Service {

    /**
     * S3 配置
     */
    private final S3Properties s3Properties;

    /**
     * S3 客户端
     */
    private final AmazonS3 amazonS3;

    public ZebraS3ServiceImpl(S3Properties s3Properties, AmazonS3 amazonS3) {
        this.s3Properties = s3Properties;
        this.amazonS3 = amazonS3;
    }

    @Override
    public void upload(String key, File file) throws S3Exception {
        Assert.hasText(key, "key must not be empty");
        Assert.notNull(file, "file must not be null");

        String bucket = s3Properties.getBucket();
        String absolutePath = file.getAbsolutePath();

        log.debug("Upload file to S3 being, bucket : {}, key : {}, file path : {}", bucket, key, absolutePath);

        try {
            amazonS3.putObject(bucket, key, file);
            log.debug("Upload file to S3 success, bucket : {}, key : {}, file path : {}", bucket, key, absolutePath);
        }
        catch (Exception e) {
            log.error("Upload file to S3 failed, bucket : {}, key : {}, file path : {}, error message: {}",
                    bucket, key, absolutePath, e.getMessage(),
                    e);
            throw new S3Exception(e);
        }
    }

    @Override
    public void upload(String key, String content) throws S3Exception {
        Assert.hasText(key, "key must not be empty");
        Assert.notNull(content, "content must not be null");

        String bucket = s3Properties.getBucket();

        log.debug("Upload string content as file to S3 begin, bucket : {}, key : {}", bucket, key);

        try {
            amazonS3.putObject(bucket, key, content);
            log.debug("Upload string content as file to S3 succeed, bucket : {}, key : {}", bucket, key);
        }
        catch (Exception e) {
            log.debug("Upload string content as file to S3 failed, bucket : {}, key : {}, error message: {}",
                    bucket, key, e.getMessage(),
                    e);
            throw new S3Exception(e);
        }
    }

    @Override
    public void upload(String key, MultipartFile multipartFile) {
        Assert.hasText(key, "key must not be empty");
        Assert.notNull(multipartFile, "multipartFile must not be null");

        try(InputStream inputStream = multipartFile.getInputStream()) {
            long size = multipartFile.getSize();
            upload(key, inputStream, size);
        } catch (IOException e) {
            log.error("Upload file to S3 failed, key : {}", key, e);
            throw new S3Exception(e);
        }
    }

    @Override
    public void upload(String key, InputStream inputStream, long contentLength) throws S3Exception {
        Assert.hasText(key, "key must not be empty");
        Assert.notNull(inputStream, "inputStream must not be null");
        Assert.isTrue(contentLength > 0, "contentLength must be greater than 0");

        log.debug("Upload file to S3 begin, key : {}, contentLength : {}", key, contentLength);
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentLength(contentLength);

        try {
            amazonS3.putObject(s3Properties.getBucket(), key, inputStream, objectMetadata);
            log.debug("Upload file to S3 success, key : {}, contentLength : {}", key, contentLength);
        }
        catch (Exception e) {
            log.error("Upload file to S3 failed, key : {}, contentLength : {}, error message : {}",
                    key, contentLength, e.getMessage(),
                    e);
            throw new S3Exception(e);
        }
    }

    @Override
    public void download(String key, OutputStream outputStream) throws S3Exception {
        Assert.hasText(key, "key must not be empty");
        Assert.notNull(outputStream, "outputStream must not be null");

        String bucket = s3Properties.getBucket();

        log.debug("Download file from S3 begin, bucket: {}, key : {}", bucket, key);

        try (S3Object s3Object = amazonS3.getObject(bucket, key);
             InputStream inputStream = s3Object.getObjectContent()) {
            byte[] buffer = new byte[1024 * 3];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }

            log.debug("Download file from S3 succeed, bucket: {}, key : {}", bucket, key);
        }
        catch (Exception e) {
            log.error("Download s3 file failed, bucket: {}, key : {}, error message : {}",
                    bucket, key, e.getMessage(),
                    e);
            throw new S3Exception(e);
        }
    }

    @Override
    public String downloadAsString(String key) throws S3Exception {
        Assert.hasText(key, "key must not be empty");

        String bucket = s3Properties.getBucket();

        log.debug("Get s3 file as string begin, bucket : {}, key : {}", bucket, key);
        try {
            String content = amazonS3.getObjectAsString(bucket, key);
            log.debug("Get s3 file as string succeed, bucket : {}, key : {}", bucket, key);

            return content;
        }
        catch (Exception e) {
            log.error("Get s3 file as string failed, bucket: {}, key : {}, error message : {}",
                    bucket, key, e.getMessage(),
                    e);
            throw new S3Exception(e);
        }

    }

    @Override
    public void delete(String key) throws S3Exception {
        Assert.hasText(key, "key must not be empty");

        String bucket = s3Properties.getBucket();

        log.debug("Delete file from S3 begin, bucket: {}, key:{}", bucket, key);

        if (!amazonS3.doesObjectExist(bucket, key)) {
            log.debug("S3 file does not exist, no need to delete, bucket: {}, key:{}", bucket, key);
            return;
        }

        try {
            amazonS3.deleteObject(bucket, key);
            log.debug("Delete file from S3 succeed, bucket: {}, key:{}", bucket, key);
        }
        catch (Exception e) {
            log.error("Delete s3 file failed, bucket: {}, key:{}, error message : {}",
                    bucket, key, e.getMessage(),
                    e);
            throw new S3Exception(e);
        }
    }

    @Override
    public void batchDelete(List<String> keys) throws S3Exception {
        Assert.notEmpty(keys, "keys must not be empty");
        int index = 0;
        for (String key : keys) {
            Assert.hasText(key, "key must not be empty, index : " + index);
            index++;
        }

        log.debug("Batch delete s3 files begin, keys : {}", String.join(",", keys));

        String bucket = s3Properties.getBucket();
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucket);
        deleteObjectsRequest.setBucketName(bucket);
        deleteObjectsRequest.withKeys(keys.toArray(new String[]{}));

        try {
            amazonS3.deleteObjects(deleteObjectsRequest);
            log.debug("Batch delete s3 files succeed, bucket: {}, keys : {}", bucket, String.join(",", keys));
        }
        catch (Exception e) {
            log.error("Batch delete s3 file failed, bucket: {}, keys:{}, error message: {}",
                    bucket, keys, e.getMessage(),
                    e);
            throw new S3Exception(e);
        }
    }

    @Override
    public void copy(String srcKey, String destKey) throws S3Exception {
        Assert.hasText(srcKey, "srcKey must not be empty");
        Assert.hasText(destKey, "destKey must not be empty");

        String bucket = s3Properties.getBucket();

        log.debug("Copy file from S3 begin, bucket: {}, srcKey:{}, destKey:{}", bucket, srcKey, destKey);

        try {
            amazonS3.copyObject(bucket, srcKey, bucket, destKey);
            log.debug("Copy file from S3 succeed, bucket: {}, srcKey:{}, destKey:{}", bucket, srcKey, destKey);
        }
        catch (Exception e) {
            log.error("Copy s3 file failed, bucket: {}, srcKey:{}, destKey:{}, error message : {}",
                    bucket, srcKey, destKey, e.getMessage(),
                    e);
            throw new S3Exception(e);
        }
    }

    @Override
    public boolean exist(String key) throws S3Exception {
        String bucket = s3Properties.getBucket();

        try {
            return amazonS3.doesObjectExist(bucket, key);
        }
        catch (Exception e) {
            log.error("Check file exist failed, bucket: {}, key:{}, error message : {}",
                    bucket, key, e.getMessage(),
                    e);
            throw new S3Exception(e);
        }
    }

    @Override
    public List<String> listKeys(String prefix) throws S3Exception {
        String bucket = s3Properties.getBucket();

        try {
            ObjectListing objectListing = amazonS3.listObjects(bucket, prefix);
            List<S3ObjectSummary> objectSummaries = objectListing.getObjectSummaries();
            return objectSummaries.stream().map(S3ObjectSummary::getKey).collect(Collectors.toList());
        }
        catch (Exception e) {
            log.error("list object's keys failed, bucket: {}, prefix:{}, error message : {}",
                    bucket, prefix, e.getMessage(),
                    e);
            throw new S3Exception(e);
        }
    }
}
